//
// Created by Administrator on 2021/7/31.
// 3d vector use float
//

#ifndef OPENGLLEARN_VECTOR3_H
#define OPENGLLEARN_VECTOR3_H

#include <algorithm>

class Vector3
{
public:
    // ctor
    Vector3() : Vector3(0.0f, 0.0f, 0.0f) {}
    explicit Vector3(float v) : Vector3(v, v, v) {}
    Vector3(float x, float y, float z) : x_{x}, y_{y}, z_{z} {}
    Vector3(const Vector3& rhs) = default;
    Vector3(Vector3&& rhs) = default;

    // functions
    float Length() const
    {
        return sqrtf(x() * x() + y() * y() + z() * z());
    }
    inline float Length2() const
    {
        return x() * x() + y() * y() + z() * z();
    }
    void Norm();
    Vector3 Normalize() const;
    float Dot(const Vector3& rhs) const
    {
        return x() * rhs.x() + y() * rhs.y() + z() * rhs.z();
    }
    Vector3 Cross(const Vector3& rhs) const
    {
        return {
                y() * rhs.z() - z() * rhs.y(),
                z() * rhs.x() - x() * rhs.z(),
                x() * rhs.y() - y() * rhs.x()
        };
    }
    inline void GetData(float* data) const;

    // static functions
    static float Dot(const Vector3& lhs, const Vector3& rhs)
    {
        return lhs.x() * rhs.x() + lhs.y() * rhs.y() + lhs.z() * rhs.z();
    }
    static Vector3 Cross(const Vector3& lhs, const Vector3& rhs)
    {
        return {
                lhs.y() * rhs.z() - lhs.z() * rhs.y(),
                lhs.z() * rhs.x() - lhs.x() * rhs.z(),
                lhs.x() * rhs.y() - lhs.y() * rhs.x()
        };
    }
    static Vector3 Zero() { return {0, 0, 0}; }
    static Vector3 Up() { return {0, 1, 0}; }
    static Vector3 Down() { return {0, -1, 0}; }
    static Vector3 Left() { return {1, 0, 0}; }
    static Vector3 Right() { return {-1, 0, 0}; }
    static Vector3 Forward() { return {0, 0, 1}; }
    static Vector3 Back() { return {0, 0, -1}; }

    // operators
    Vector3& operator= (const Vector3& rhs) = default;
    Vector3& operator= (Vector3&& rhs) = default;

    Vector3 operator+ (const Vector3& rhs)
    {
        return {
            x() + rhs.x(),
            y() + rhs.y(),
            z() + rhs.z()
        };
    }
    Vector3& operator+= (const Vector3& rhs)
    {
        x() += rhs.x();
        y() += rhs.y();
        z() += rhs.z();
        return *this;
    }
    Vector3 operator- (const Vector3& rhs)
    {
        return {
            x() - rhs.x(),
            y() - rhs.y(),
            z() - rhs.z(),
        };
    }
    Vector3& operator-= (const Vector3& rhs)
    {
        x() -= rhs.x();
        y() -= rhs.y();
        z() -= rhs.z();
        return *this;
    }
    Vector3 operator* (const float& rhs)
    {
        return {
            x() * rhs,
            y() * rhs,
            z() * rhs
        };
    }
    Vector3& operator*= (const float& rhs)
    {
        x() *= rhs;
        z() *= rhs;
        y() *= rhs;
        return *this;
    }
    Vector3 operator/ (const float& rhs)
    {
        return {
                x() / rhs,
                y() / rhs,
                z() / rhs
        };
    }
    Vector3& operator/= (const float& rhs)
    {
        x() /= rhs;
        y() /= rhs;
        z() /= rhs;
        return *this;
    }
    Vector3 operator- () const
    {
        return {
                -x(),
                -y(),
                -z()
        };
    }

    float& operator[] (int i)
    {
        switch (i)
        {
            case 0:
                return x();
            case 1:
                return y();
            case 2:
                return z();
            default:
                throw;
        }
    }

    // getters and setters
    float& x() { return x_; }
    float& y() { return y_; }
    float& z() { return z_; }
    const float& x() const { return x_; }
    const float& y() const { return y_; }
    const float& z() const { return z_; }

private:
    float x_;
    float y_;
    float z_;
};

#endif //OPENGLLEARN_VECTOR3_H
